/*
 * Description:DFXagent管理类
 */
#include <fstream>
#include <tchar.h>
#include <thread>
#include <sstream>
#include <sys/stat.h>
#include <map>
#include <mutex>
#include "system_log_receiver.h"
#include "common.h"
#include "log.h"
#include "shellapi.h"
#include "error.h"
#include <shlwapi.h>
#include "power_consumption/application_monitor.h"

using namespace std;
using namespace COMMON;

namespace SYSLOGRECV {

bool SysLogRecv::stopReceiveSysLog = false;
std::string SysLogRecv::dfxFolder = "\\\\HwFs\\dfx_folder";
std::string SysLogRecv::systemRoot = "";
std::string SysLogRecv::localAppData = "";
std::unique_ptr<std::mutex> SysLogRecv::dfxMutex = make_unique<std::mutex>();
int SysLogRecv::Init()
{
    LOG_INFO("DFX Manager Init");
    systemRoot = getenv("SystemRoot");
    localAppData = getenv("LOCALAPPDATA");
    std::thread dfxthread(SysLogRecv::ExecuteDFXThread);
    dfxthread.detach();
    LOG_INFO("DFX Manager thread out");
    return OK;
}

void SysLogRecv::QuickStartInit()
{
    LOG_INFO("DFX Quick Init");
    std::thread dfxthread(SysLogRecv::QuickStartDFXThread);
    dfxthread.detach();
}

void SysLogRecv::Quit()
{
    stopReceiveSysLog = true;
    LOG_INFO("Quit SysLogRecv");
}

void SysLogRecv::ExecuteDFXThread()
{
    LOG_INFO("execute dfx shell");
    FetchMinidump();
    // 循环文件传输
    while (!stopReceiveSysLog) {
        FetchDFXInfo();
        Sleep(FETCH_LOG_INTERVAL);
    }
}

void SysLogRecv::QuickStartDFXThread()
{
    LOG_INFO("start quick start DFX Thread");
    FetchMinidump();
    FetchDFXInfo();
}

void SysLogRecv::FetchMinidump()
{
    LOG_INFO("attempt to fetch minidump");
    std::unique_lock<std::mutex> lock(*dfxMutex);
    struct stat info;
    for (int i = 0; i < RECHECK_DFX_FOLDER_MAX_TIMES; i++) {
        if (stat(dfxFolder.c_str(), &info) != 0) {
            LOG_INFO("dfx folder not exist recheck later...");
            Sleep(RECHECK_DFX_FOLDER_INTERVAL);
            continue;
        }
        LOG_INFO("found dfx folder");
        MkdirFolders();
        CopyFileMinidump();
        break;
    }
}

void SysLogRecv::FetchDFXInfo()
{
    LOG_INFO("start copying files...");
    struct stat info;
    std::unique_lock<std::mutex> lock(*dfxMutex);
    // 创建子文件夹
    if (stat(dfxFolder.c_str(), &info) != 0) {
        LOG_INFO("dfx folder not exist");
        return;
    }
    MkdirFolders();
    if (!CheckDfxFolderSize()) {
        return;
    }
    CheckFileMinidump();
    CopyFileAgentDump();
    CopyFileVmagentLog();
    CopyfileEtl();
    CopyFileEvtx();
    CopyVmgtLog();
}

int SysLogRecv::MkdirFolders()
{
    std::string vmagentLog = dfxFolder + "\\vmagent_log";
    std::string miniDump = dfxFolder + "\\minidump";
    std::string etl = dfxFolder + "\\etl";
    std::string evtx = dfxFolder + "\\evtx";
    std::string vmgt = dfxFolder + "\\vmgt";
    if (!PathFileExists(vmagentLog.c_str())) {
        bool flag1 = CreateDirectory(vmagentLog.c_str(), nullptr);
    }
    if (!PathFileExists(miniDump.c_str())) {
        bool flag2 = CreateDirectory(miniDump.c_str(), nullptr);
    }
    if (!PathFileExists(etl.c_str())) {
        bool flag3 = CreateDirectory(etl.c_str(), nullptr);
    }
    if (!PathFileExists(evtx.c_str())) {
        bool flag4 = CreateDirectory(evtx.c_str(), nullptr);
    }
    if (!PathFileExists(vmgt.c_str())) {
        bool flag5 = CreateDirectory(vmgt.c_str(), nullptr);
    }
    return OK;
}

// Vmagent log全部取回
int SysLogRecv::CopyFileVmagentLog()
{
    string sourceFileAgentService = R"(C:\ProgramData\VMAgent\log\agent_service.log)";
    string descFileAgentService = dfxFolder + "\\vmagent_log\\agent_service.log";
    string sourceFileAgentService1 = R"(C:\ProgramData\VMAgent\log\agent_service.1.log)";
    string descFileAgentService1 = dfxFolder + "\\vmagent_log\\agent_service.1.log";
    string sourceFileSetup = R"(C:\ProgramData\VMAgent\log\setup.log)";
    string descFileSetup = dfxFolder + "\\vmagent_log\\setup.log";
    string sourceFileUpdate = R"(C:\ProgramData\VMAgent\log\update.log)";
    string descFileUpdate = dfxFolder + "\\vmagent_log\\update.log";
    string copyCmdAgentService = "copy " + sourceFileAgentService + " " + descFileAgentService;
    string copyCmdAgentService1 = "copy " + sourceFileAgentService1 + " " + descFileAgentService1;
    string copyCmdSetup = "copy " + sourceFileSetup + " " + descFileSetup;
    string copyCmdUpdate = "copy " + sourceFileUpdate + " " + descFileUpdate;
    if (!IsFileGreater(sourceFileAgentService, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmdAgentService.c_str());
    }
    if (!IsFileGreater(sourceFileAgentService1, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmdAgentService1.c_str());
    }
    if (!IsFileGreater(sourceFileSetup, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmdSetup.c_str());
    }
    if (!IsFileGreater(sourceFileUpdate, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmdUpdate.c_str());
    }
    return OK;
}

// 检查文件minidump文件个数
int SysLogRecv::CheckFileMinidump()
{
    std::string destinationDir = dfxFolder + "\\minidump";
    int num = GetFileNumInData(destinationDir);
    // 判断是否大于10，大于10则删除最老文件
    if (num > FILE_RECV_COUNT_MINIDUMP) {
        RemoveOldFile(destinationDir);
    }
    return OK;
}

// 全部取回，取回后共享文件夹内文件个数<=10
int SysLogRecv::CopyFileMinidump()
{
    std::string destinationPath = dfxFolder + "\\minidump";
    std::string sourcePath = systemRoot + "\\Minidump";
    std::vector<std::string> srcFiles = GetNewestFiles(sourcePath, FILE_RECV_COUNT_MINIDUMP, "", FILE_SIZE_UNIT_MIB);
    DeleteFiles(destinationPath, "dmp");
    CopyFiles(sourcePath, destinationPath, srcFiles);
    return OK;
}

// 全部取回，取回后共享文件夹内dump个数<=5
int SysLogRecv::CopyFileAgentDump()
{
    std::string destinationPath = dfxFolder + "\\vmagent_log";
    std::string sourcePath = R"(C:\ProgramData\VMAgent\log)";
    std::vector<std::string> srcFiles = GetNewestFiles(
        sourcePath, FILE_RECV_COUNT_AGENTDUMP, "dmp", MAX_FILE_SIZE_AGENT_DUMP_FILE);
    DeleteFiles(destinationPath, "dmp");
    CopyFiles(sourcePath, destinationPath, srcFiles);
    return OK;
}

// 全部取回，取回后共享文件夹内文件个数<=10
int SysLogRecv::CopyfileEtl()
{
    std::string destinationDir = dfxFolder + "\\etl";
    std::string sourcePath = R"(C:\Windows\Logs\WindowsUpdate\)";
    std::vector<std::string> srcFiles = GetNewestFiles(sourcePath, FILE_RECV_COUNT_ETL, "", MAX_FILE_SIZE_ETL_FILE);
    std::vector<std::string> destFiles = GetNewestFiles(
        destinationDir, FILE_RECV_COUNT_ETL, "", MAX_FILE_SIZE_ETL_FILE);
    // 比较两个列表，如果不同则删除并复制
    if (destFiles.size() == 0 || !equal(srcFiles.begin(), srcFiles.end(), destFiles.begin())) {
        // 2.1 清空目标文件夹
        DeleteFiles(destinationDir, "etl");
        // 2.2 执行copy操作 todo 复制指定文件
        CopyFiles(sourcePath, destinationDir, srcFiles);
    }
    return OK;
}

// 全部取回
int SysLogRecv::CopyFileEvtx()
{
    std::string sourceFileSystem = R"(C:\Windows\System32\winevt\Logs\System.evtx)";
    std::string descFileSystem = dfxFolder + "\\evtx\\System.evtx";
    std::string sourceFileApplication = R"(C:\Windows\System32\winevt\Logs\Application.evtx)";
    std::string descFileApplication = dfxFolder + "\\evtx\\Application.evtx";
    string copyCmdSystem = "copy " + sourceFileSystem + " " + descFileSystem;
    string copyCmdApplication = "copy " + sourceFileApplication + " " + descFileApplication;
    if (!IsFileGreater(sourceFileSystem, MAX_FILE_SIZE_EVTX_FILE)) {
        system(copyCmdSystem.c_str());
    }
    if (!IsFileGreater(sourceFileApplication, MAX_FILE_SIZE_EVTX_FILE)) {
        system(copyCmdApplication.c_str());
    }
    return OK;
}

// 获取文件个数
int SysLogRecv::GetFileNumInData(const std::string &path)
{
    WIN32_FIND_DATA findFileData;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    int count = 0;
    std::string findPath = path + "\\*.*";

    // 搜索文件夹中的所有文件
    hFind = FindFirstFile(findPath.c_str(), &findFileData);
    if (hFind == INVALID_HANDLE_VALUE) {
        LOG_INFO("open folder failed");
        return -1;
    }
    // 遍历所有文件并计数
    do {
        if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            count++;
        }
    } while (FindNextFile(hFind, &findFileData) != 0);
    // 关闭句柄
    FindClose(hFind);
    hFind = INVALID_HANDLE_VALUE;
    return count;
}

// 获取文件创建时间，找到最老文件
int SysLogRecv::RemoveOldFile(const std::string &folderPath)
{
    WIN32_FIND_DATAA findData;
    HANDLE hFind;
    unsigned long oldFileCreateTime = ULONG_MAX;
    std::string removeFileName = "";
    std::string findPath = folderPath + "\\*.*";
    hFind = FindFirstFileA(findPath.c_str(), &findData);
    if (hFind == INVALID_HANDLE_VALUE) {
        return -1;
    }
    do {
        if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            if (oldFileCreateTime > findData.ftCreationTime.dwLowDateTime) {
                oldFileCreateTime = findData.ftCreationTime.dwLowDateTime;
                removeFileName = findData.cFileName;
            }
        }
    } while (FindNextFileA(hFind, &findData));
    std::string removePath = folderPath + "\\" + removeFileName;
    DeleteFile(removePath.c_str());
    FindClose(hFind);
    hFind = INVALID_HANDLE_VALUE;
    return 0;
}

int SysLogRecv::GetDirFileNum(const std::string &dirPath)
{
    struct _finddata_t fileinfo;
    std::string current = dirPath + "\\*.*";
    long long handle = _findfirst(current.c_str(), &fileinfo);
    int fileNum = 0;
    if (handle == -1) {
        return fileNum;
    }
    while (!_findnext(handle, &fileinfo)) {
        if (strcmp(fileinfo.name, "..") == 0 || (fileinfo.attrib & _A_SUBDIR)) {
            continue;
        }
        fileNum++;
    }
    _findclose(handle);
    return fileNum;
}

int SysLogRecv::CopyVmgtLog()
{
    std::string sourceFile = R"(C:\ProgramData\VMGT\log\vmgt.log)";
    std::string descFile = dfxFolder + "\\vmgt\\vmgt.log";
    std::string sourceFile1 = R"(C:\ProgramData\VMGT\log\vmgt.1.log)";
    std::string descFile1 = dfxFolder + "\\vmgt\\vmgt.1.log";
    string copyCmd = "copy " + sourceFile + " " + descFile;
    string copyCmd1 = "copy " + sourceFile1 + " " + descFile1;
    if (!IsFileGreater(sourceFile, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmd.c_str());
    }
    if (!IsFileGreater(sourceFile1, MAX_FILE_SIZE_COMMON_LOG)) {
        system(copyCmd1.c_str());
    }
    return OK;
}

void SysLogRecv::GetFileNames(const std::string &dirName, std::set<std::string> &filesNameSet)
{
    // 获取路径下的文件名，放入nameSet
    struct _finddata_t fileinfo;
    std::string current = dirName + "\\*.*";
    long long handle = _findfirst(current.c_str(), &fileinfo);
    if (handle == -1) {
        return;
    }
    while (!_findnext(handle, &fileinfo)) {
        if (strcmp(fileinfo.name, "..") == 0 || (fileinfo.attrib & _A_SUBDIR)) {
            continue;
        }
        filesNameSet.insert(fileinfo.name);
    }
    _findclose(handle);
}

bool SysLogRecv::CompareMtime(const _finddata_t &file1, const _finddata_t &file2)
{
    return file1.time_write > file2.time_write;
}

vector<std::string> SysLogRecv::GetNewestFiles(
    const std::string &dirName, int n, const std::string &extension, int maxSize)
{
    struct _finddata_t fileinfo;
    vector<_finddata_t> files;
    std::string current = dirName + "\\*.*";
    long long handle = _findfirst(current.c_str(), &fileinfo);
    if (handle == -1) {
        _findclose(handle);
        return {};
    }
    int length = extension.length();
    while (!_findnext(handle, &fileinfo)) {
        if (strcmp(fileinfo.name, "..") == 0 || (fileinfo.attrib & _A_SUBDIR)) {
            continue;
        }
        std::string fileName = fileinfo.name;
        std::string fileExtension = fileName.substr(fileName.find_last_of(".") + 1);
        if (extension != "" && fileExtension != extension) {
            continue;
        }
        if (fileinfo.size > maxSize) {
            continue;
        }
        files.push_back(fileinfo);
    }
    _findclose(handle);
    std::sort(files.begin(), files.end(), CompareMtime);
    if (files.size() > n) {
        files.erase(files.begin() + n, files.end());
    }
    vector<std::string> fileNames;
    for (auto fi : files) {
        fileNames.push_back(fi.name);
    }
    return fileNames;
}

// 删除指定后缀文件
void SysLogRecv::DeleteFiles(const std::string &folderPath, const std::string &extension)
{
    if (folderPath.empty() || extension.empty()) {
        return;
    }
    struct _finddata_t fileinfo;
    std::string current = folderPath + "\\*.*";
    long long handle = _findfirst(current.c_str(), &fileinfo);
    if (handle == -1) {
        return;
    }
    int length = extension.length();
    while (!_findnext(handle, &fileinfo)) {
        if (strcmp(fileinfo.name, "..") == 0 || (fileinfo.attrib & _A_SUBDIR)) {
            continue;
        }
        std::string fileName = fileinfo.name;
        std::string fileExtension = fileName.substr(fileName.find_last_of(".") + 1);
        if (strncmp(fileExtension.c_str(), extension.c_str(), length) != 0) {
            continue;
        }
        std::remove((folderPath + "\\" + fileName).c_str());
    }
    _findclose(handle);
}

int SysLogRecv::CopyFiles(
    const std::string &sourcePath, const std::string &desPath, const std::vector<std::string> &files)
{
    std::stringstream fileNamesStream;
    for (std::string file : files) {
        fileNamesStream << file << " ";
        std::string copyCmd = "xcopy /Y /E /S " + sourcePath + "\\" + file.c_str() + " " + desPath;
        system(copyCmd.c_str());
    }
    LOG_INFO("copied files: {}", fileNamesStream.str());
    return OK;
}

bool SysLogRecv::CheckDfxFolderSize()
{
    long long currentSize = GetFolderSize(dfxFolder);
    if (currentSize < 0) {
        return false;
    }
    if (currentSize > MAX_FILE_SIZE_DFX_FOLDER) {
        return false;
    }
    return true;
}

long long SysLogRecv::GetFolderSize(const std::string &folderPath)
{
    long long size = 0;
    _finddata_t fileInfo;
    string path = folderPath + "\\*.*";
    intptr_t handle = _findfirst(path.c_str(), &fileInfo);
    if (handle == -1) {
        return -1;
    }

    while (!_findnext(handle, &fileInfo)) {
        if (fileInfo.attrib & _A_SUBDIR) {
            if (strcmp(fileInfo.name, ".") != 0 && strcmp(fileInfo.name, "..") != 0) {
                string subFolderPath = folderPath + "\\" + fileInfo.name;
                size += GetFolderSize(subFolderPath);
            }
        } else {
            size += fileInfo.size;
        }
    }
    _findclose(handle);
    return size;
}

// 判断文件是否超限
bool SysLogRecv::IsFileGreater(const std::string &filePath, int setFileSize)
{
    HANDLE hFile =
        CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        return false;
    }
    DWORD fileSize = GetFileSize(hFile, NULL);
    CloseHandle(hFile);
    hFile = INVALID_HANDLE_VALUE;
    return fileSize > setFileSize;
}

}  // namespace SYSLOGRECV